﻿using System;
using System.Collections.Generic;
using System.Data;
using Newtonsoft.Json;

namespace Syncronization.REST
{
    public class DataTableConverter : JsonConverter
    {
        #region Fields

        private const string ColumnsProperty = "Columns";
        private const string ItemArrayProperty = "ItemArray";
        private const string RowsProperty = "Rows";
        private const string RowStateProperty = "RowState";
        private const string TableNameProperty = "TableName";

        #endregion

        #region public

        /// <summary>
        /// Determines whether this instance can convert the specified value type.
        /// </summary>
        /// <param name="valueType">Type of the value.</param>
        /// <returns>
        ///     <c>true</c> if this instance can convert the specified value type; otherwise, <c>false</c>.
        /// </returns>
        public override bool CanConvert(Type valueType)
        {
            return typeof (DataTable).IsAssignableFrom(valueType);
        }

        public void ReadDataRow(JsonReader reader, DataRow dataRow)
        {
            while (reader.TokenType != JsonToken.EndObject)
            {
                reader.Read();
                if (reader.TokenType == JsonToken.PropertyName && (string) reader.Value == RowStateProperty)
                {
                    reader.Read();
                    var rowState = (string) reader.Value;
                    if (rowState == DataRowState.Added.ToString())
                    {
                        dataRow.AcceptChanges();
                        dataRow.SetAdded();
                    }
                    else if (rowState == DataRowState.Deleted.ToString())
                    {
                        dataRow.Delete();
                    }
                    else if (rowState == DataRowState.Modified.ToString())
                    {
                        dataRow.AcceptChanges();
                        dataRow.SetModified();
                    }
                    else if (rowState == DataRowState.Unchanged.ToString())
                    {
                        dataRow.AcceptChanges();
                    }
                }
                else if (reader.TokenType == JsonToken.PropertyName && (string) reader.Value == ItemArrayProperty)
                {
                    reader.Read();

                    var items = new List<object>();

                    //at start of array
                    while (reader.TokenType != JsonToken.EndArray)
                    {
                        reader.Read();
                        if (reader.TokenType != JsonToken.EndArray)
                            items.Add(reader.Value);
                    }

                    dataRow.ItemArray = items.ToArray();
                }
            }
        }

        /// <summary>
        /// Reads the JSON representation of the object.
        /// </summary>
        /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
        /// <param name="objectType">Type of the object.</param>
        /// <returns>The object value.</returns>
        public override object ReadJson(JsonReader reader, Type objectType)
        {
            var dataTable = new DataTable();

            while (reader.TokenType != JsonToken.EndObject)
            {
                reader.Read();
                if (reader.TokenType == JsonToken.PropertyName && (string) reader.Value == ColumnsProperty)
                {
                    var columnConverter = new DataColumnConverter();
                    reader.Read();
                    if (reader.TokenType == JsonToken.StartArray)
                    {
                        reader.Read();
                        while (reader.TokenType != JsonToken.EndArray)
                        {
                            var dataColumn = (DataColumn) columnConverter.ReadJson(reader, typeof (DataColumn));
                            if (dataColumn != null)
                                dataTable.Columns.Add(dataColumn);

                            //Puts us at start of the next object or end of array
                            if (reader.TokenType == JsonToken.EndObject)
                                reader.Read();
                        }
                    }
                }
                else if (reader.TokenType == JsonToken.PropertyName && (string) reader.Value == RowsProperty)
                {
                    reader.Read();
                    if (reader.TokenType == JsonToken.StartArray)
                    {
                        reader.Read();
                        while (reader.TokenType != JsonToken.EndArray)
                        {
                            DataRow dataRow = dataTable.NewRow();
                            dataTable.Rows.Add(dataRow);

                            ReadDataRow(reader, dataRow);

                            //Puts us at start of the next object or end of array
                            if (reader.TokenType == JsonToken.EndObject)
                                reader.Read();
                        }
                    }
                }
                else if (reader.TokenType == JsonToken.PropertyName && (string) reader.Value == TableNameProperty)
                {
                    reader.Read();
                    dataTable.TableName = (string) reader.Value;
                }
            }

            return dataTable;
        }

        /// <summary>
        /// Writes the JSON representation of the object.
        /// </summary>
        /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
        /// <param name="value">The value.</param>
        public override void WriteJson(JsonWriter writer, object value)
        {
            var table = value as DataTable;
            var columnConverter = new DataColumnConverter();

            writer.WriteStartObject();

            writer.WritePropertyName(TableNameProperty);
            writer.WriteValue(table.TableName);

            writer.WritePropertyName(ColumnsProperty);
            writer.WriteStartArray();
            foreach (object column in table.Columns)
            {
                columnConverter.WriteJson(writer, column);
            }
            writer.WriteEndArray();

            writer.WritePropertyName(RowsProperty);
            writer.WriteStartArray();

            foreach (DataRow row in table.Rows)
            {
                WriteDataRow(writer, row);
            }

            writer.WriteEndArray();

            writer.WriteEndObject();
        }

        #endregion

        #region private

        private void WriteDataRow(JsonWriter writer, object value)
        {
            var row = value as DataRow;

            writer.WriteStartObject();

            DataRowState rowState = row.RowState;

            writer.WritePropertyName(RowStateProperty);
            writer.WriteValue(rowState.ToString());

            //If the row is deleted then we need to reject changes
            //otherwise the row will throw an exception when accessing
            //and data from it.
            //Whoeever came up with the idea of throwing an exception on deleted rows
            //should be punched in the gut.
            if (rowState == DataRowState.Deleted)
                row.RejectChanges();

            writer.WritePropertyName(ItemArrayProperty);

            var ser = new JsonSerializer();
            ser.Serialize(writer, row.ItemArray);

            writer.WriteEndObject();
        }

        #endregion
    }
}